package com.atguigu.gmall.gateway.filter;/*
 * @author: XueYouPeng
 * @time: 23.9.13 下午 8:47
 */


import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.gateway.properties.UserAuthProperties;
import com.atstudent.gmall.common.constant.GmallConstant;
import com.atstudent.gmall.user.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Configuration
public class UserAuthFilter implements GlobalFilter , Ordered {


    @Autowired
    private UserAuthProperties userAuthProperties;

    //规则匹配器
    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private RedisTemplate<String , String> redisTemplate;

    /**
     * 拦截所有请求
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //拿到请求路径
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String path = serverHttpRequest.getURI().getPath();

        //判断不需要登录的静态资源
        //通过一个方法 AntPathMatcher来匹配
        List<String> noauthurls = userAuthProperties.getNoauthurls();
        for (String noauthurl : noauthurls){
            //参数一：网关中配置的规则 ， 参数二：前端传过来路径
            boolean match = antPathMatcher.match(noauthurl, path);
            if (match){
                //放行
                return chain.filter(exchange);
            }
        }

        // 需要验证用户登录的路径规则的处理
        List<String> authurls = userAuthProperties.getAuthurls();
        for (String authurl : authurls){

            //进行匹配
            boolean match = antPathMatcher.match(authurl, path);

            if (match){
                //如果匹配上了 要验证登录
                //用token来判断是否登录
                String token = getToken(exchange);
                //token为空，则未登录，踢到登录界面
                if (StringUtils.isEmpty(token)){
                    return locationUrl(exchange , userAuthProperties.getToLoginPage());
                }else {
                    //有token 校验其合法性
                    //通过token 从Redis中查询有没有对应的用户数据
                    UserInfo userInfo = getUserInfoByToken(token);
                    if (userInfo == null){
                        //说明是伪造的token 请求重定向 踢到登录页面
                        return locationUrl(exchange , userAuthProperties.getToLoginPage());
                    }else {
                        //放行 并且透传id
                        return userIdThrought(exchange , chain , userInfo);
                    }
                }
            }
        }


        // 普通资源： 就是用户在登录和未登录状态下都可以进行访问的资源
        //拿到token
        String token = getToken(exchange);
        if (StringUtils.isEmpty(token)){
            //说明用户未登录 直接透传临时用户的id 并放行
            return userTempIdThrought(exchange , chain);
        }else {
            //用户登录 判断token合法性-->根据token查询用户信息
            UserInfo userInfo = getUserInfoByToken(token);
            if (userInfo == null){
                //说明token不合法 伪令牌
                //踢到登录页面
                return locationUrl(exchange , userAuthProperties.getToLoginPage());
            }else {
                //合法登录 透传id且放行
                return userIdThrought(exchange , chain , userInfo);
            }
        }

    }

    //透传临时用户id 并且放行
    private Mono<Void> userTempIdThrought(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取临时用户的id
        String userTempId = getUserTempId(exchange);

        //创建一个新的request对象
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()
                .header("userTempId", userTempId)
                .build();
        //创建一个新的exchange对象
        ServerWebExchange webExchange = exchange.mutate().request(serverHttpRequest).build();
        return chain.filter(webExchange);
    }

    //获取临时用户的id
    //前端在未登录访问购物车时会生成临时id  存在cookie中
    private String getUserTempId(ServerWebExchange exchange) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        HttpCookie httpCookie = serverHttpRequest.getCookies().getFirst("userTempId");
        if (httpCookie != null){
            return httpCookie.getValue();
        }else {
            String userTempId = serverHttpRequest.getHeaders().getFirst("userTempId");
            return userTempId;
        }
    }

    //透传的思路：创建一个新的Request对象，
    // 然后在该对象的请求头中添加一个新的请求头userId ,
    // 在放行的时候使用新的request对象
    private Mono<Void> userIdThrought(ServerWebExchange exchange, GatewayFilterChain chain, UserInfo userInfo) {

        //还要透传临时用户的id
        String userTempId = getUserTempId(exchange);

        // 创建一个新的Request对象
        // mutate方法获取一个建造者对象
        ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate()
                .header("userId", String.valueOf(userInfo.getId()))
                .header("userTempId" , userTempId)
                .build();
        //创建一个新的exchange对象
        ServerWebExchange webExchange = exchange.mutate().request(serverHttpRequest).build();

        //放行 使用新的exchange对象
        return chain.filter(webExchange);
    }

    //把用户踢到登录页面
    private Mono<Void> locationUrl(ServerWebExchange exchange, String toLoginPage) {

        //实现思路：底层原理 --> 302状态码 + location响应头(在header里设置)
        //用到 ServerHttpResponse这个对象
        ServerHttpResponse serverHttpResponse = exchange.getResponse();
        //设置302状态码
        serverHttpResponse.setStatusCode(HttpStatus.FOUND);

        //获取访问当前资源的请求路径
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String originUrl = serverHttpRequest.getURI().getPath().toString();
        //设置location响应头
        serverHttpResponse.getHeaders().set("location" , toLoginPage + "?originUrl=" + originUrl);

        //给serverHttpResponse添加新的cookie
        //domain代表哪个域
        ResponseCookie tokenResponseCookie = ResponseCookie.from("token", "").domain("gmall.com").maxAge(-1).path("/").build();
        ResponseCookie userInfoResponseCookie = ResponseCookie.from("userInfo", "").domain("gmall.com").maxAge(-1).path("/").build();
        serverHttpResponse.addCookie(tokenResponseCookie);
        serverHttpResponse.addCookie(userInfoResponseCookie);

        return serverHttpResponse.setComplete();
    }

    //根据token获取用户信息
    private UserInfo getUserInfoByToken(String token) {
        //拿到用户的JSON字符串
        String userInfoJSON = redisTemplate.opsForValue().get(GmallConstant.REDIS_USER_LOGIN_PRE + token);
        if (StringUtils.isEmpty(userInfoJSON)){
            return null;
        }else {
            UserInfo userInfo = JSON.parseObject(userInfoJSON, UserInfo.class);
            return userInfo;
        }
    }

    /*
     *   前端传递token的方式：
     *  1、通过请求头(header)进行传递
     *  2、通过Cookie进行传递的
     */
    private String getToken(ServerWebExchange exchange) {
        //拿到ServerHttpRequest对象
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        //先看cookie中有米有
        HttpCookie httpCookie = serverHttpRequest.getCookies().getFirst("token");
        if (httpCookie != null){
            return httpCookie.getValue();//返回token
        }else {//cookie中没有 从header请求头中拿取
            String token = serverHttpRequest.getHeaders().getFirst("token");
            return token;
        }
    }


    //定义过滤器的编号，后期框架会调用该方法获取当前过滤器的编号，
    // 然后根据这个编号对过滤器进行排序，
    // 后期就按照排序以后的结果依次执行对应的过滤器
    @Override
    public int getOrder() {
        return -1;
    }
}
